package voldTransfo;

import java.util.HashMap;
import java.util.Map;

import org.eclipse.emf.common.util.EList;
import org.eclipse.uml2.uml.Class;
import org.eclipse.uml2.uml.DataType;
import org.eclipse.uml2.uml.Enumeration;
import org.eclipse.uml2.uml.EnumerationLiteral;
import org.eclipse.uml2.uml.Interface;
import org.eclipse.uml2.uml.Operation;
import org.eclipse.uml2.uml.Package;
import org.eclipse.uml2.uml.Property;
import org.eclipse.uml2.uml.Type;
import org.eclipse.uml2.uml.VisibilityKind;

import uml2Java.IllegalTypeException;
import uml2Java.IllegalVisibityException;
import uml2Java.JavaChargeur;
import uml2Java.JavaIncompatibleTypeException;
import uml2Java.NonAbstractMethodException;
import uml2Java.NonStaticVariableException;
import uml2Java.StaticMethodException;
import uml2Java.UMLChargeur;

import astjava.AbstractTypeDeclaration;
import astjava.BodyDeclaration;
import astjava.ClassDeclaration;
import astjava.EnumConstantDeclaration;
import astjava.EnumDeclaration;
import astjava.InheritanceKind;
import astjava.InterfaceDeclaration;
import astjava.JavaFactory;
import astjava.MethodDeclaration;
import astjava.Model;
import astjava.PrimitiveType;
import astjava.UnresolvedTypeDeclaration;
import astjava.VariableDeclarationFragment;

public class OldUML2Java {

	private Package _UMLpackageRacine;
	
	private Model _modeleCible;
	
	private Map<String,String> _correspondanceTypesPrimitifs;

	public Map<String, String> get_correspondanceTypesPrimitifs() {
		return _correspondanceTypesPrimitifs;
	}

	public Model get_modeleCible() {
		return _modeleCible;
	}
	
	public Package get_UMLpackageRacine() {
		return _UMLpackageRacine;
	}
	
	public OldUML2Java() {
		_modeleCible = JavaFactory.eINSTANCE.createModel();
		
		PrimitiveType typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeBoolean();
		typePrimitif.setName("boolean");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeByte();
		typePrimitif.setName("byte");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeChar();
		typePrimitif.setName("char");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeDouble();
		typePrimitif.setName("double");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeFloat();
		typePrimitif.setName("float");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeInt();
		typePrimitif.setName("int");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeLong();
		typePrimitif.setName("long");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveTypeShort();
		typePrimitif.setName("short");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		typePrimitif = JavaFactory.eINSTANCE.createPrimitiveType();
		typePrimitif.setName("String");
		_modeleCible.getOrphanTypes().add(typePrimitif);
		
		_correspondanceTypesPrimitifs = new HashMap<String,String>();
		_correspondanceTypesPrimitifs.put("EBoolean", "boolean");
		_correspondanceTypesPrimitifs.put("EByte", "byte");
		_correspondanceTypesPrimitifs.put("EChar", "char");
		_correspondanceTypesPrimitifs.put("EDouble", "double");
		_correspondanceTypesPrimitifs.put("EFloat", "float");
		_correspondanceTypesPrimitifs.put("EInt", "int");
		_correspondanceTypesPrimitifs.put("ELong", "long");
		_correspondanceTypesPrimitifs.put("EShort", "short");
		_correspondanceTypesPrimitifs.put("EString", "String");

	}

	public OldUML2Java(String source) {
		this();
		_UMLpackageRacine = new UMLChargeur(source).loadUMLPackage();
	}

	public void packageUML2PackageJava() {
		 astjava.Package javaPackageRacine = JavaFactory.eINSTANCE.createPackage();
		 javaPackageRacine.setName(_UMLpackageRacine.getName());
		 _modeleCible.getOwnedElements().add(javaPackageRacine);
		 try {
			transformTypes(_UMLpackageRacine, javaPackageRacine);
		} catch (JavaIncompatibleTypeException e) {
			e.printStackTrace();
		} catch (IllegalVisibityException e) {
			e.printStackTrace();
		} catch (NonStaticVariableException e) {
			e.printStackTrace();
		} catch (IllegalTypeException e) {
			e.printStackTrace();
		} catch (NonAbstractMethodException e) {
			e.printStackTrace();
		} catch (StaticMethodException e) {
			e.printStackTrace();
		}
		 packageUML2PackageJavaRecursif(_UMLpackageRacine, javaPackageRacine);
	}

	private void packageUML2PackageJavaRecursif(Package umlPackage,
			astjava.Package javaPackage) {
		EList<Package> umlPackages = umlPackage.getNestedPackages();
		for (Package umlNestedPackage : umlPackages) {
			astjava.Package javaNestedPackage = JavaFactory.eINSTANCE.createPackage();
			javaNestedPackage.setName(umlNestedPackage.getName());
			javaPackage.getOwnedPackages().add(javaNestedPackage);
			try {
				transformTypes(umlNestedPackage, javaNestedPackage);
			} catch (JavaIncompatibleTypeException e) {
				e.printStackTrace();
			} catch (IllegalVisibityException e) {
				e.printStackTrace();
			} catch (NonStaticVariableException e) {
				e.printStackTrace();
			} catch (IllegalTypeException e) {
				e.printStackTrace();
			} catch (NonAbstractMethodException e) {
				e.printStackTrace();
			} catch (StaticMethodException e) {
				e.printStackTrace();
			}
			packageUML2PackageJavaRecursif(umlNestedPackage, javaNestedPackage);
		}
	}

	public void transformTypes(Package umlPackage, astjava.Package javaPackage) throws JavaIncompatibleTypeException, IllegalVisibityException, NonStaticVariableException, IllegalTypeException, NonAbstractMethodException, StaticMethodException {
		EList<Type> UMLTypes = umlPackage.getOwnedTypes();
		for (Type t : UMLTypes) {
			if (t instanceof Class) {
				javaPackage.getOwnedElements().add(transformClass((Class) t));
			}
			else if (t instanceof Interface) {
				javaPackage.getOwnedElements().add(transformInterface((Interface) t));
			}
			else if (t instanceof Enumeration) {
				javaPackage.getOwnedElements().add(transformEnumeration((Enumeration) t));
			}
			else if (t instanceof DataType) {
				javaPackage.getOwnedElements().add(transformDataType((DataType) t));
			}
			else {
				throw new JavaIncompatibleTypeException(t);
			}
		}
	}

	private AbstractTypeDeclaration transformDataType(DataType t) {
		UnresolvedTypeDeclaration dataTypeDeclaration = JavaFactory.eINSTANCE.createUnresolvedTypeDeclaration();
		dataTypeDeclaration.setName(t.getName());
		return dataTypeDeclaration;
	}

	private AbstractTypeDeclaration transformEnumeration(Enumeration t) throws IllegalVisibityException {
		validateVisibilityOnType(t);
		astjava.EnumDeclaration javaEnumeration = JavaFactory.eINSTANCE.createEnumDeclaration();
		setType(t, javaEnumeration);
		transformLiteralsEnumeration(t, javaEnumeration);
		return javaEnumeration;
	}

	private void transformLiteralsEnumeration(Enumeration umlEnum,
			EnumDeclaration javaEnumeration) {
		for (EnumerationLiteral umlLiteral : umlEnum.getOwnedLiterals()) {
			EnumConstantDeclaration javaLiteral = JavaFactory.eINSTANCE.createEnumConstantDeclaration();
			javaLiteral.setName(umlLiteral.getName());
			javaEnumeration.getBodyDeclarations().add(javaLiteral);
		}
	}

	private void validateVisibilityOnType(Type t)
			throws IllegalVisibityException {
		if (t.getVisibility().equals(VisibilityKind.PRIVATE_LITERAL) ||
				t.getVisibility().equals(VisibilityKind.PROTECTED_LITERAL)) {
			throw new IllegalVisibityException(t);
		}
	}

	private void setType(Type t, astjava.AbstractTypeDeclaration javaType) {
		javaType.setName(t.getName());
		javaType.setModifier(JavaFactory.eINSTANCE.createModifier());
		javaType.getModifier().setVisibility(transformVisibility(t.getVisibility()));
	}

	private AbstractTypeDeclaration transformInterface(Interface t) throws IllegalVisibityException, NonStaticVariableException, IllegalTypeException, NonAbstractMethodException, StaticMethodException {
		validateVisibilityOnType(t);
		astjava.InterfaceDeclaration javaInterface = JavaFactory.eINSTANCE.createInterfaceDeclaration();
		setType(t, javaInterface);
		transformAttributesInterface(t, javaInterface);
		transformMethodsInterface(t, javaInterface);
		return javaInterface;
	}

	private void transformMethodsInterface(Interface umlInterface,
			InterfaceDeclaration javaInterface) throws NonAbstractMethodException, IllegalVisibityException, StaticMethodException {
		for (Operation operation : umlInterface.getOperations()) {
			validateOperationInInterface(operation);
			javaInterface.getBodyDeclarations().add(transformOperation(operation));
		}
	}

	private BodyDeclaration transformOperation(Operation umlOperation) {
		MethodDeclaration javaMethod = JavaFactory.eINSTANCE.createMethodDeclaration();
		javaMethod.setName(umlOperation.getName());
		javaMethod.setModifier(JavaFactory.eINSTANCE.createModifier());
		javaMethod.getModifier().setVisibility(transformVisibility(umlOperation.getVisibility()));
		javaMethod.getModifier().setStatic(umlOperation.isStatic());
		if (umlOperation.isAbstract())
			javaMethod.getModifier().setInheritance(InheritanceKind.ABSTRACT);
		else 
			javaMethod.getModifier().setInheritance(InheritanceKind.NONE);
		return javaMethod;
	}

	private void validateOperationInInterface(Operation operation) throws NonAbstractMethodException, IllegalVisibityException, StaticMethodException {
		if (operation.isStatic()) {
			throw new StaticMethodException(operation);
		}
		if (!operation.getVisibility().equals(VisibilityKind.PUBLIC_LITERAL)) {
			throw new IllegalVisibityException(operation);
		}
		if (!operation.isAbstract()) {
			throw new NonAbstractMethodException(operation);
		}

	}

	private void transformAttributesInterface(Interface umlInterface,
			InterfaceDeclaration javaInterface) throws NonStaticVariableException, IllegalVisibityException, IllegalTypeException {
		for (Property attribut : umlInterface.getAttributes()) {
			validateAttributeInInterface(attribut);
			javaInterface.getBodyDeclarations().add(transformAttribute(attribut));
		}
	}

	private void transformAttributesClass(Class umlClass,
			ClassDeclaration javaClass) throws IllegalTypeException {
		for (Property attribut : umlClass.getAttributes()) {
			javaClass.getBodyDeclarations().add(transformAttribute(attribut));
		}
	}

	private BodyDeclaration transformAttribute(Property attribut) throws IllegalTypeException {
		astjava.FieldDeclaration attributJava = JavaFactory.eINSTANCE.createFieldDeclaration();
		attributJava.setName(attribut.getName());
		attributJava.setModifier(JavaFactory.eINSTANCE.createModifier());
		attributJava.getModifier().setVisibility(transformVisibility(attribut.getVisibility()));
		attributJava.getModifier().setStatic(attribut.isStatic());
		attributJava.getType().setType(transformType(attribut.getType()));
		return attributJava;
	}

	private astjava.Type transformType(Type type) throws IllegalTypeException {
		if (_correspondanceTypesPrimitifs.containsKey(type.getName()))
			return findPrimitiveType(_correspondanceTypesPrimitifs.get(type.getName()));
		else 
			return findModelType(type.getName());
	}

	private astjava.Type findModelType(String umlType) throws IllegalTypeException{
		//TODO: Marche Pas
		for (astjava.Type javaType : _modeleCible.getOrphanTypes())
			if (javaType.getName().equals(umlType))
				return javaType;
		throw new IllegalTypeException(umlType);
	}

	private astjava.Type findPrimitiveType(String umlType) throws IllegalTypeException {
		for (astjava.Type javaType : _modeleCible.getOrphanTypes())
			if (javaType.getName().equals(umlType))
				return javaType;
		throw new IllegalTypeException(umlType);
	}

	private void validateAttributeInInterface(Property attribut) throws NonStaticVariableException, IllegalVisibityException {
		if (!attribut.isStatic()) {
			throw new NonStaticVariableException(attribut);
		}
		if (attribut.getVisibility().equals(VisibilityKind.PRIVATE_LITERAL) ||
				attribut.getVisibility().equals(VisibilityKind.PROTECTED_LITERAL)) {
			throw new IllegalVisibityException(attribut);
		}
	}

	private AbstractTypeDeclaration transformClass(Class t) throws IllegalVisibityException, IllegalTypeException {
		validateVisibilityOnType(t);
		astjava.ClassDeclaration javaClass = JavaFactory.eINSTANCE.createClassDeclaration();
		setType(t, javaClass);
		if (t.isAbstract())
			javaClass.getModifier().setInheritance(InheritanceKind.ABSTRACT);
		transformAttributesClass(t, javaClass);
		transformMethodsClass(t, javaClass);
		return javaClass;
	}
	
	private void transformMethodsClass(Class umlClass, ClassDeclaration javaClass) {
		for (Operation operation : umlClass.getOperations()) {
			
			javaClass.getBodyDeclarations().add(transformOperation(operation));
		}
	}

	private astjava.VisibilityKind transformVisibility(VisibilityKind umlVisibility) {
		if (umlVisibility.equals(VisibilityKind.PUBLIC_LITERAL))
			return astjava.VisibilityKind.PUBLIC;
		if (umlVisibility.equals(VisibilityKind.PACKAGE_LITERAL))
			return astjava.VisibilityKind.NONE;
		if (umlVisibility.equals(VisibilityKind.PROTECTED_LITERAL))
			return astjava.VisibilityKind.PROTECTED;
		if (umlVisibility.equals(VisibilityKind.PRIVATE_LITERAL))
			return astjava.VisibilityKind.PRIVATE;
		return null;
	}
	
	public static void main(String[] args) {
		OldUML2Java uml2java = new OldUML2Java("modele_de_test/test.uml");
		uml2java.packageUML2PackageJava();
		JavaChargeur chargeur = new JavaChargeur( "model/uml2java.astjava");
		chargeur.loadJavaModel();
		chargeur.save(uml2java.get_modeleCible());
	}

}
